/*  BatAppService.Java - A service that monitors changes in battery levels
        Copyright (C) 2009 Devendra Laulkar

        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation version 2 of the License. 

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.intellBatt.services;

import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;

import com.intellBatt.CriticalGUI;
import com.intellBatt.LocationView;
import com.intellBatt.R;
import com.intellBatt.Settings;

public class BattAppService extends Service 
{

	public static final int DEFAULT_LEVEL = 40;
	public static final int LOW_LEVEL = 5; // Below this level, will not try to determine location
	public static final String locI = "com.IntelBat.BATT";
	public static final String locSend = "com.IntelBat.services.LOCATION";
	public static final String KEY_WARN = "warnlevel";
	public static final long DEFAULT_RECHECK = 60000 * 2; /* 2 minute */
	public static final long DEFAULT_RENOTIFY = 60000 * 60; /* 2 minute */
	//public static final long DEFAULT_RENOTIFY = 
	public static final long DEFAULT_LONGCHECK = 60000 * 60; /* 1 hour */
	//public static final long DEFAULT_LONGCHECK = 60000 * 5; /* 1 hour */
	
	public static final String KEY_NAME = "name";
	public static final String KEY_ID = "idd";
	protected static final double RETRAIN_PROB_LEVEL = 0.2;
	protected static final int RENOTIFY_LEVEL = 2;

	Date d = new Date();
	
	public boolean usbConn = false;
	public boolean acConn = false;
	public int level, warnLevel, levelSentOut;
	boolean allTime, nextDay;
	private int startHr, startMin, endHr, endMin;
	
	private boolean registered = false;
	private boolean notified = false;
	NotificationManager notifMgr;
	private long lastNotified = 0;
	
	private Timer timer = new Timer();
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	private BroadcastReceiver locReceiver = new BroadcastReceiver()
	{

		public void onReceive(Context ctx, Intent mIntent)
		{
			
			log("**Got back intent from ML");
			Bundle extras = mIntent.getExtras();
			if(extras == null)
			{
				return;
			}

			String origAction = extras.getString("action");
			String locName = extras.getString(KEY_NAME);
			if("check".equalsIgnoreCase(origAction))
			{
				/* No location was found */
				if(locName == null || "".equals(locName))
				{
					/* But we have started charging, so store this location */
					if(usbConn || acConn)
					{
						log("Plugged in, but loc not found, so training");
						prepareAndSendTrainingRequest();
						notified = false;
					}
					/* No location found, and we are not charging - retry again */
					else
					{
						if(!notified)
						{
						scheduleScan(new Date(System.currentTimeMillis() + DEFAULT_RECHECK));
						}
					}
				}
				/* A bat location was found - decide if we should notify user */
				else
				{
					double prob = extras.getDouble(MLService.KEY_PROB, -1);
					log( "Loc found " + locName + " prob is " + prob);
					/* We are plugged in - Check if we need to retrain */
					if(usbConn || acConn)
					{

						if(prob < RETRAIN_PROB_LEVEL)
						{
							log( "Retraining loc " + locName);
							prepareAndSendRetrainRequest(extras.getLong(MLService.KEY_ID, -1));
						}

					}
					/* We are not plugged in -  Decide we should notify the user */
					else
					{
						if(isBattLocation(locName) && level <= warnLevel)
						{
							log( "Batt low and at charging location");
							
							if( (!notified) || (System.currentTimeMillis() > lastNotified + DEFAULT_RENOTIFY))
							{
								log( "**Notify User - At batt charging place");
								if(notified)
								{
									log("Renotifying at level" + level);
								}
								levelSentOut = level;
								notified = true;
								//notifyLowLevel();
								createAndSendNotification(getString(R.string.notify), extras);
								
								scheduleLongScan(new Date(System.currentTimeMillis() + DEFAULT_LONGCHECK));
							}
							else
							{
								log("Not notifying user - was notified recently or level is not less than warnLevel");
							}
						}
					}

				}
			}

			else if ("train".equalsIgnoreCase(origAction))
			{
				// Then we need to add the location to our db
				if(locName == null || "".equals(locName))
				{
					return ; // Should not happen
				}
				log( "Training done, trained loc " + locName);
				//addNewLocToDB(locName, locName);
			}
		}
	};


	private BroadcastReceiver mBatReceiver = new BroadcastReceiver(){

		public void onReceive(Context ctx, Intent mIntent)
		{
			int plugged;

			Bundle b = mIntent.getExtras();
			level = b.getInt("level");
			log( "Level" + level);

			plugged = b.getInt("plugged");
			if(plugged == BatteryManager.BATTERY_PLUGGED_AC)
			{
				log( "PLUGGED AC");
				acConn = true;
			}
			else
			{
				acConn = false;
			}

			 //DEBUG
			if(plugged == BatteryManager.BATTERY_PLUGGED_USB)
			{
				log( "PLUGGED USB");

				usbConn = true;
			}
			else
			{
				usbConn = false;
			}
			//usbConn = false;
			onBattChange();
		}
	};


	private void onBattChange()
	{
		/* Rules go here */
		if(acConn || usbConn)
		{
			notifMgr.cancelAll();
			notified = false;
			prepareAndSendLocationRequest();
			return;
		}
		
		/* If we have very low battery life, then don't do anything */
		if(level < LOW_LEVEL)
		{
			return;
		}
		/* If we are on threshold of low battery life, send a final alert */
		if(level == LOW_LEVEL)
		{
			/* Play RED ALERT */
			/* Nofity user, and inform that no more alerts will be sent */
			notifyLowLevel();
			return;
		}
		
		/* Else, check for locations, and warn */
		//if(level <= TEST_LEVEL)
		if(level <= warnLevel)
		{
			if(!usbConn && !acConn && !notified)
			{
				log( "Batt Level low, checking for locations");
				prepareAndSendLocationRequest();
			}
			if(notified)
			{
				log("Batt below low level, but already notified, so not checking");
			}
		}
		/* Once we are charged, clear all notifications */
		/* May be redundant */
		if(level > DEFAULT_LEVEL)
		{
			notified = false;
		}
	}
	
	private void scheduleScan(Date nextCheck)
	{
			timer.cancel();
			timer = new Timer();
			TimerTask timerTask =  new TimerTask() 
			{

				@Override
				public void run() {
					prepareAndSendLocationRequest();
				}
			};

			timer.schedule(timerTask, nextCheck);
	}
	
	private void scheduleLongScan(Date nextCheck)
	{
		timer.cancel();
		timer = new Timer();
		TimerTask timerTask =  new TimerTask() 
		{

			@Override
			public void run() {
				notified = false;
				prepareAndSendLocationRequest();
			}
		};

		timer.schedule(timerTask, nextCheck);
		
	}
	private boolean isBattLocation(String locName)
	{	
		return true;
	}

	private void prepareAndSendRetrainRequest(long id)
	{
		Intent mIntent = new Intent(locSend);
		Bundle b = new Bundle();
		b.putString("action", "retrain");
		b.putString("returnIntent", locI);
		b.putLong(KEY_ID, id);
		mIntent.putExtras(b);
		log( "Sending re-training request");
		startService(mIntent);
	}
	private void prepareAndSendLocationRequest() {

		Intent mIntent = new Intent(locSend);
		Bundle b = prepareLocationRequestBundle();
		mIntent.putExtras(b);
		log( "Sending location request");
		startService(mIntent);
		//this.sendBroadcast(mIntent);
	}


	private void prepareAndSendTrainingRequest()
	{
		Intent mIntent = new Intent(locSend);
		Bundle b = new Bundle();
		b.putString("action", "train");
		b.putString("returnIntent", locI);
		mIntent.putExtras(b);
		log( "Sending training request");
		startService(mIntent);
		//this.sendBroadcast(mIntent);
	}


	private Bundle prepareLocationRequestBundle()
	{
		Bundle b = new Bundle();
		b.putString("action", "check");
		b.putString("returnIntent", locI);
		return b;		
	}
	public void onStart(Intent i, int ss)
	{
		super.onStart(i, ss);
		log( "registering receiver");
		registerListners();
		levelSentOut = -1;

		loadSettings();
	}

	private void registerListners() {
		if(!registered)
		{
			this.registerReceiver(mBatReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
			this.registerReceiver(locReceiver, new IntentFilter(locI));
			notifMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
			//alert= MediaPlayer.create(this, R.raw.redalert);
			registered = true;
		}
	}

	private void loadSettings() {
		SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
		warnLevel = settings.getInt(Settings.KEY_ALERT_LEVEL, DEFAULT_LEVEL);
		
		log("LoadSettings - WarnLevel is" + warnLevel);
		
		//int startHr, startMin, endHr, endMin;
		startHr = settings.getInt(Settings.KEY_START_HOUR,Settings.DEFAULT_START_HOUR);
		startMin = settings.getInt(Settings.KEY_START_MIN,Settings.DEFAULT_START_MIN);
		endHr = settings.getInt(Settings.KEY_END_HOUR,Settings.DEFAULT_END_HOUR);
		endMin = settings.getInt(Settings.KEY_END_MIN, Settings.DEFAULT_END_MIN);
		allTime = false;
		nextDay = false;
		
		if(startHr == endHr && startMin == endMin)
		{
			allTime = true;
		}
		
		if(endHr <= startHr )
		{
			nextDay = true;
			if(endHr == startHr && endMin > startMin)
				nextDay = false;
		}
	}

	private void notifyLowLevel() {
	
		//createAndSendNotification(R.string.lowLevelAlert, locationName, id)
		
		Notification notif = new Notification(R.drawable.stat_sys_battery_10,
				getText(R.string.criticalNotification), System.currentTimeMillis());

		Context context = getApplicationContext();
		CharSequence contentTitle = "Intelligent Battery monitor";
		CharSequence contentText = getText(R.string.criticalNotification);
		Intent notificationIntent = new Intent(this, CriticalGUI.class);
		//notificationIntent.putExtra(LocationView.NAME,locationName);
		//notificationIntent.putExtra(LocationView, value)
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
		notif.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
		notif.flags = notif.flags | Notification.FLAG_AUTO_CANCEL;
		notif.vibrate = new long[] {10, 2500, 100, 1000};
		notifMgr.notify(1, notif);
		MediaPlayer mp = MediaPlayer.create(this, R.raw.redalert);
		
		// Set this to what the user has configured
		// TODO
		AudioManager mng = (AudioManager)getSystemService(AUDIO_SERVICE);
		int warnVolume = mng.getStreamVolume(AudioManager.STREAM_RING);
		mp.setVolume(warnVolume, warnVolume);
		mp.start();
		
	}
	public void createAndSendNotification(String message, Bundle extras)
	{

		lastNotified = System.currentTimeMillis();
		Notification notif = new Notification(R.drawable.stat_sys_battery_20,
				message, lastNotified);

		Context context = getApplicationContext();
		CharSequence contentTitle = "Intelligent Battery monitor";
		CharSequence contentText = message;
		Intent notificationIntent = new Intent(this, LocationView.class);
		notificationIntent.putExtras(extras);
		//notificationIntent.putExtra(LocationView.NAME,locationName);
		//notificationIntent.putExtra(LocationView, value)
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
		notif.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
		notif.flags = notif.flags | Notification.FLAG_AUTO_CANCEL;
		if(allTime || inWarnPeriod())
		{
			notif.vibrate = new long[] {10, 2500, 100, 1000};
			playAlert();
		}
		notifMgr.notify(1, notif);

	}
	
	private void playAlert() {
		MediaPlayer mp = MediaPlayer.create(this, R.raw.beep);
		// TODO - Need to set this sensibly
		AudioManager mng = (AudioManager)getSystemService(AUDIO_SERVICE);
		int warnVolume = mng.getStreamVolume(AudioManager.STREAM_RING);
		mp.setVolume(warnVolume, warnVolume);
		mp.start();
	}

	private void setNoticationByTime(Notification notif)
	{
		

	}
	public void onDestroy()
	{
		log( "Destroyed");
		this.unregisterReceiver(mBatReceiver);
		this.unregisterReceiver(locReceiver);

		super.onDestroy();
	}

	public void unregisterListeners()
	{
		if(registered)
		{
			registered = false;
			this.unregisterReceiver(mBatReceiver);
			this.unregisterReceiver(locReceiver);
		}
	}
	
	public boolean inWarnPeriod()
	{
		Calendar cal1 = Calendar.getInstance();
		Calendar cal2 = Calendar.getInstance();
		
		Calendar curr = Calendar.getInstance();
		
		if(nextDay)
		{
			cal2.roll(Calendar.DATE, true);
		}
		
		cal1.set(Calendar.HOUR_OF_DAY,startHr);
		cal1.set(Calendar.MINUTE,startMin);
		cal2.set(Calendar.HOUR_OF_DAY, endHr);
		cal2.set(Calendar.MINUTE, endMin);
		
		if(curr.after(cal1) && curr.before(cal2))
		{
			return true;
		}
		else
			return false;
		
	}
	public void log(String message)
	{
//		Log.i("BATT", message);
	}
}